﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using KgUtils.Collections;
using KgUtils.Resources;
using System.Windows.Data;

namespace KgUtils.Wpf.BaseClasses
{
    /// <summary>
    /// Classe de base pour les ViewModel.
    /// </summary>
    /// <typeparam name="TEntityBE">Le type des objets affichés/concernés par l'écran.</typeparam>
    /// <typeparam name="TView">Le type de la Vue</typeparam>
    public abstract class BaseViewModel<TEntityBE, TView> : IView<TEntityBE>, INotifyPropertyChanged
        where TEntityBE : BaseBE<TEntityBE>, IEntity
        where TView : Window
    {
        /// <summary>
        /// The View linked to the ViewModel.
        /// It might be used for occassional needs to access the View features (window closing...)
        /// </summary>
        public TView ViewSource
        {
            get;
            protected set;
        }

        /// <summary>
        /// An instance of a IPresenter class, providing access to the BS/DS (business/data access) features
        /// </summary>
        protected IPresenter presenter { get; set; }

        /// <summary>
        /// The default view attached to ListeEntites. It may allow to filter the results and manipulate the data without changing the list.
        /// It is recreated each time the underlying list changes
        /// </summary>
        protected ListCollectionView ListeEntitesView { get; set; }

        /// <summary>
        /// A list of all the entities.
        /// </summary>        
        public virtual ObservableCollection<TEntityBE> ListeEntites
        {
            get { return _listeEntites; }
            set
            {
                if (_listeEntites != value)
                {
                    _listeEntites = value;
                    OnPropertyChanged("ListeEntites");
                    this.ListeEntitesView = (ListCollectionView)CollectionViewSource.GetDefaultView(ListeEntites);
                }
            }
        }
        protected ObservableCollection<TEntityBE> _listeEntites;

        /// <summary>
        /// The current entity, generally one of the ListeEntites items.
        /// </summary>
        public virtual TEntityBE EntiteCourante
        {
            get { return _entiteCourante; }
            set { _entiteCourante = value; OnPropertyChanged("EntiteCourante"); }
        }
        protected TEntityBE _entiteCourante;

        /// <summary>
        /// Stores any error message. Mostly used for validation purposes.
        /// </summary>
        public virtual StringBuilder ErrorMessage { get; set; }
        /// <summary>
        /// Stores a list of controls that causes the error. Mostly used for validation purposes.
        /// </summary>
        public virtual List<Control> ControlesEnErreur { get; set; }

        /// <summary>
        /// Indicates if the current ViewModel is in Editing Mode.
        /// </summary>
        public bool IsEditing
        {
            get { return _isEditing; }
            set { _isEditing = value; OnPropertyChanged("IsEditing", "IsReading"); }
        }
        /// <summary>
        /// Indicates if the current ViewModel is in Reading Mode. 
        /// This is readonly as it is mapped on the IsEditing property.
        /// This is useful for binding in the View without having to invert the boolean value.
        /// </summary>
        public bool IsReading { get { return !IsEditing; } }
        protected bool _isEditing;

        /// <summary>
        /// Method used to Initialize the current ViewModel.
        /// This method must be implemented by inheriting classes.
        /// </summary>
        public abstract void Initialize();

        /// <summary>
        /// Displays the content of ErrorMessage in a MessageBox.
        /// </summary>
        public virtual void ShowErrorsMessage()
        {
            if (ErrorMessage.Length > 0)
                MessageBox.Show(ErrorMessage.ToString(), Messages.ErrorMessageBoxTitle, MessageBoxButton.OK, MessageBoxImage.Warning);
        }

        /// <summary>
        /// Saves all the items of ListeEntites to database.
        /// It calls the SauvegarderListeEntites() method of the IPresenter object.
        /// </summary>
        /// <returns></returns>
        public virtual bool SaveAll()
        {
            return presenter.SauvegarderListeEntites();
        }

        /// <summary>
        /// Saves the EntiteCourante object to database.
        /// It calls the SauvegarderEntiteCourante() method of the IPresenter object.
        /// </summary>
        /// <returns></returns>
        public virtual bool SaveEntiteCourante()
        {
            return presenter.SauvegarderEntiteCourante();
        }

        /// <summary>
        /// Controls the data of the screen (used before saving).
        /// It performs the default Validations of all the controls in the form and displays a MessageBox if any error message exists.
        /// 
        /// This method shouldn't be replaced but should be called AFTER the specific checks performed by the inheriting ViewModel.
        /// So the overriding method should just add its own checks and then call base.IsDataControlOK().
        /// </summary>
        /// <returns></returns>
        public virtual bool IsDataControlOK()
        {
            if (!WpfHelper.FormIsValid(this.ViewSource))
            {
                ErrorMessage.AppendLine("Des données sont invalides. Les erreurs sont encadrées en rouge.");
                ErrorMessage.AppendLine(String.Join(Environment.NewLine, WpfHelper.FormGetErrorMessages(this.ViewSource).Select(e => e.ErrorContent)));
            }

            if (ControlesEnErreur.Any() || ErrorMessage.Length > 0)
            {
                if (ControlesEnErreur.Any())
                    ControlesEnErreur.First().Focus();
                ShowErrorsMessage();

                ControlesEnErreur.Clear();
                ErrorMessage.Clear();
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Selects the first entity in the ListeEntites list if it is not empty
        /// </summary>
        /// <returns>Returns True if an entity has been selected. Returns false if the list is null or empty.</returns>
        public virtual bool SelectFirstEntity()
        {
            if (!this.ListeEntites.IsNullOrEmpty())
            {
                this.EntiteCourante = this.ListeEntites.First();
                this.EntiteCourante.IsSelected = true;
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Calls the ChargerDonneesDansListe() method of the IPresenter object, without any parameter.
        /// This should have the effect of loading the data in the ListeEntites list.
        /// </summary>
        public virtual void ChargerDonneesDansListe()
        {
            presenter.ChargerDonneesDansListe(null);
        }

        /// <summary>
        /// Loads secondary lists content (combobox data...).
        /// </summary>
        protected virtual void ChargerDonneesDansListeReference()
        {
        }

        /// <summary>
        /// Initializes the ViewModel ICommand objects.
        /// </summary>
        protected virtual void InitialiserCommandes()
        {
        }

        /// <summary>
        /// Sets the max length of the controls, if necessary.
        /// </summary>
        public virtual void FixZonesSize()
        {
        }

        /// <summary>
        /// Reloads the ListeEntites list and tries to select pEntity if provided, ou the first element if pEntity is provided but can't be found.
        /// The search is based on the IEntities.Id property. So this property should be implemented.
        /// </summary>
        /// <param name="pEntity">The entity that should be selected after refresh.</param>
        public virtual void RefreshListe(TEntityBE pEntity = null)
        {
            this.ChargerDonneesDansListe();

            //On sélectionne l'élément s'il est fourni
            if (pEntity != null)
            {
                var item = this.ListeEntites.FirstOrDefault(e => e.Id == pEntity.Id);
                if (item != null)
                {
                    this.EntiteCourante = item;
                    this.EntiteCourante.IsSelected = true;
                }
                else
                {
                    SelectFirstEntity();
                }
            }
        }

        /// <summary>
        /// Initializes the data of the ViewModel.
        /// </summary>
        /// <param name="pView">The View object attached to this ViewModel</param>
        /// <param name="pIsEditing">Specifies if the View starts straight in the Editing mode or Reading mode (default value)</param>
        public BaseViewModel(TView pView, bool pIsEditing = false)
        {
            this.ErrorMessage = new StringBuilder();
            this.ControlesEnErreur = new List<Control>();
            this.ViewSource = pView;
            this.IsEditing = pIsEditing;
        }

        /// <summary>
        /// Closes the view attached to this ViewModel
        /// </summary>
        /// <param name="pDialogResult">The value that is returned from the System.Windows.Window.ShowDialog(). Can be null.</param>
        public void CloseView(bool? pDialogResult = null)
        {
            if (this.ViewSource != null)
                this.ViewSource.DialogResult = pDialogResult;
        }

        #region INotifyPropertyChanged Membres
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Allows to fire several PropertyChanged events in one call.
        /// </summary>
        /// <param name="propertyNames"></param>
        protected virtual void OnPropertyChanged(params string[] propertyNames)
        {
            foreach (string propertyName in propertyNames)
                this.OnPropertyChanged(propertyName);
        }

        #endregion
    }
}
